﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace memcached
{
    /// <summary>
    /// Synchronized cache access facility.
    /// Thread safe, process-wide only sync.
    /// </summary>
    public sealed class SyncMemCache : IDisposable
    {
        /// <summary>
        /// Initialize with default memcached client.
        /// </summary>
        [System.Diagnostics.DebuggerHidden]
        public SyncMemCache() { }

        /// <summary>
        /// Initialize with provided client.
        /// </summary>
        /// <param name="c"></param>
        [System.Diagnostics.DebuggerHidden]
        public SyncMemCache(ICache c)
        {
            if (c == null)
                throw new ArgumentNullException("c");
            client = c;
        }

        #region Housekeeping
        private ICache client;
        private bool owner;

        private static readonly Dictionary<string, System.Threading.ReaderWriterLockSlim> locks =
            new Dictionary<string, System.Threading.ReaderWriterLockSlim>(1);

        private void Initialize()
        {
            lock (this)
                if (client == null)
                {
                    owner = true;
                    client = new MemCache();
                }
        }

        /// <summary>
        /// </summary>
        public void Dispose()
        {
            GC.SuppressFinalize(this);
            if (owner && client != null && client is IDisposable)
                lock (this)
                    if (client != null)
                    {
                        ((IDisposable)client).Dispose();
                        client = null;
                    }
        }
        #endregion

        /// <summary>
        /// Get value of an item within a process-wide read-lock.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="success">true if operation succeeded</param>
        /// <returns>null if not found</returns>
        public T Get<T>(string key, out bool success)
        {
            if (client == null)
                Initialize();
            System.Threading.ReaderWriterLockSlim rw = GetLockFor(key);
            rw.EnterReadLock();
            try
            {
                return client.Get<T>(key, out success);
            }
            finally
            {
                rw.ExitReadLock();
            }
        }

        /// <summary>
        /// Perform operation on an item within a write lock.
        /// </summary>
        /// <typeparam name="T">type of value</typeparam>
        /// <param name="key">not null</param>
        /// <param name="setOperation">locked operation to perform</param>
        /// <returns>result of operation</returns>
        public bool Set<T>(string key, Func<ICache, string, bool> setOperation)
        {
            if (setOperation == null)
                throw new ArgumentNullException("setOperation");
            if (client == null)
                Initialize();
            System.Threading.ReaderWriterLockSlim rw = GetLockFor(key);
            rw.EnterWriteLock();
            try
            {
                return setOperation(client, key);
            }
            finally
            {
                rw.ExitWriteLock();
            }
        }

        /// <summary>
        /// Set an item within a write-lock.
        /// </summary>
        /// <typeparam name="T">type of value</typeparam>
        /// <param name="key">not null</param>
        /// <param name="valueProvider">to return new value for an item</param>
        /// <returns>true if value (not null) successfully set</returns>
        public bool Set<T>(string key, Func<string, T> valueProvider)
        {
            if (valueProvider == null)
                throw new ArgumentNullException("valueProvider");
            if (client == null)
                Initialize();
            System.Threading.ReaderWriterLockSlim rw = GetLockFor(key);
            rw.EnterWriteLock();
            try
            {
                object val = valueProvider(key);
                if (val != null)
                    return client.Set(key, val);
                return false;
            }
            finally
            {
                rw.ExitWriteLock();
            }
        }

        /// <summary>
        /// Get locking (process-wide) facility for a given key.
        /// </summary>
        /// <param name="key"></param>
        /// <returns>not null</returns>
        public static System.Threading.ReaderWriterLockSlim GetLockFor(string key)
        {
            System.Threading.ReaderWriterLockSlim rw;
            lock (locks)// todo we can try RW lock here
            {
                if (!locks.TryGetValue(key, out rw))
                {
                    rw = new System.Threading.ReaderWriterLockSlim(System.Threading.LockRecursionPolicy.NoRecursion);
                    locks.Add(key, rw);
                }
            }
            return rw;
        }
        
        /// <summary>
        /// Dispose inactive locks.
        /// </summary>
        public static void ClearLocks()
        {
            lock (locks)
            {
                string[] keys = locks.Keys.ToArray();
                foreach (string key in keys)
                {
                    System.Threading.ReaderWriterLockSlim rw = locks[key];
                    if (rw.IsReadLockHeld || rw.IsWriteLockHeld ||
                        rw.WaitingReadCount > 0 || rw.WaitingWriteCount > 0)
                        continue;
                    locks.Remove(key);
                    rw.Dispose();
                }
            }
        }
    }
}